/*******************************************************************************
 * Copyright 2010 Prometheus Consulting
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package nz.co.senanque.validationengine.metadata;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import nz.co.senanque.validationengine.ValidationUtils;
import nz.co.senanque.validationengine.annotations.ChoiceList;
import nz.co.senanque.validationengine.annotations.Description;
import nz.co.senanque.validationengine.annotations.Inactive;
import nz.co.senanque.validationengine.annotations.Label;
import nz.co.senanque.validationengine.annotations.MapField;
import nz.co.senanque.validationengine.annotations.ReadOnly;
import nz.co.senanque.validationengine.annotations.Required;
import nz.co.senanque.validationengine.choicelists.ChoiceBase;
import nz.co.senanque.validationengine.fieldvalidators.FieldValidator;

import org.jdom.Document;
import org.jdom.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;

/**
 * Generates the static metadata for all relevant classes
 * 
 * @author Roger Parkinson
 * @version $Revision: 1.17 $
 */
public class AnnotationsMetadataFactory implements FactoryBean, MessageSourceAware
{
	private static final Logger log = LoggerFactory.getLogger(AnnotationsMetadataFactory.class);
	
	private transient List<Class<?>> m_classes;
    private transient Document m_choicesDocument;
    private transient Map<String,List<ChoiceBase>> m_choicesMap;


    private transient MessageSource m_messageSource;

	public List<Class<?>> getClasses() 
	{
		return m_classes;
	}

	public void setClasses(final List<Class<?>> classes) 
	{
		m_classes = classes;
	}

	public Object getObject() throws Exception {
	    
	    createChoicesMap();
	    
	    final Map<Class<Annotation>, Class<? extends FieldValidator<Annotation>>> validatorMap = getValidatorMap();
	    final Map<Class<?>,ClassMetadata> classMap = new HashMap<Class<?>,ClassMetadata>();

		for (Class<?> clazz: m_classes)
		{
			log.debug("class name {}",clazz);
			boolean classNeeded = true;
			final ClassMetadata classMetadata = new ClassMetadata();		
			for (Method method: clazz.getMethods())
			{
				if (method.getName().startsWith("get"))
				{
				    final String fieldName = ValidationUtils.getFieldNameWithJavaCase(method.getName().substring(3));
                    if (fieldName.equals("metadata"))
                    {
                        continue;
                    }
                    if (fieldName.equals("class"))
                    {
                        continue;
                    }
                    final PropertyMetadataImpl fieldMetadata = new PropertyMetadataImpl(clazz,fieldName,m_messageSource);
					boolean fieldNeeded = false;
					for (Annotation fieldAnnotation: method.getAnnotations())
					{
						log.debug("field annotation {}",fieldAnnotation);
                        if (fieldAnnotation instanceof Label)
                        {
                            fieldMetadata.setLabelName(((Label)fieldAnnotation).labelName());
                            fieldNeeded = true;
                        }
                        if (fieldAnnotation instanceof Inactive)
                        {
                            fieldMetadata.setInactive(true);
                            fieldNeeded = true;
                        }
                        if (fieldAnnotation instanceof ReadOnly)
                        {
                            fieldMetadata.setReadOnly(true);
                            fieldNeeded = true;
                        }
                        if (fieldAnnotation instanceof Required)
                        {
                            fieldMetadata.setRequired(true);
                            fieldNeeded = true;
                        }
//						if (fieldAnnotation instanceof Range)
//						{
//							fieldMetadata.setMinValue(((Range)fieldAnnotation).minValue());
//							fieldMetadata.setMaxValue(((Range)fieldAnnotation).maxValue());
//							fieldNeeded = true;
//						}
						if (fieldAnnotation instanceof Description)
						{
							fieldMetadata.setDescription(((Description)fieldAnnotation).name());
							fieldNeeded = true;
						}
//						if (fieldAnnotation instanceof Regex)
//						{
//							fieldMetadata.setRegex(((Regex)fieldAnnotation).regex());
//							fieldNeeded = true;
//						}
//						if (fieldAnnotation instanceof BeanValidator)
//						{
//							fieldMetadata.setBean(((BeanValidator)fieldAnnotation).bean());
//							fieldMetadata.setParam(((BeanValidator)fieldAnnotation).param());
//							fieldNeeded = true;
//						}
                        if (fieldAnnotation instanceof MapField)
                        {
                            fieldMetadata.setMapField(((MapField)fieldAnnotation).name());
                            fieldNeeded = true;
                        }
                        if (fieldAnnotation instanceof ChoiceList)
                        {
                            final List<ChoiceBase> choiceList = m_choicesMap.get(((ChoiceList)fieldAnnotation).name());
                            fieldMetadata.setChoiceList(choiceList);
                            fieldNeeded = true;
                        }
                        Class<? extends FieldValidator<Annotation>> fvClass = validatorMap.get(fieldAnnotation.annotationType());
                        if (fvClass != null)
                        {
                            final FieldValidator<Annotation> fv = fvClass.newInstance();
                            fv.init(fieldAnnotation,fieldMetadata);
                            fieldMetadata.addConstraintValidator(fv);
                            fieldNeeded = true;
                       }                        
					}
                    Class<?> returnClass = method.getReturnType();
                    Object[] t = returnClass.getEnumConstants();
                    if (t != null)
                    {
                        fieldNeeded = true;
                        fieldMetadata.setChoiceList(t);
                    }
					if (!fieldNeeded)
					{
						if (m_classes.contains(returnClass) || returnClass.isAssignableFrom(List.class))
						{
							fieldNeeded = true;
						}
					}
					if (fieldNeeded)
					{
						classMetadata.addField(fieldName,fieldMetadata);
						classNeeded = true;
					}
				}
			}
			if (classNeeded)
			{
				classMap.put(clazz, classMetadata);
			}
		}
		return new EngineMetadata(classMap);
	}
	
    private void createChoicesMap()
    {
        // TODO Auto-generated method stub
        
    }

//    private void figureTableRestriction(TableRestrictionBuilder tableRestrictionBuilder,Map<Class<?>,ClassMetadata> classMap) throws Exception
//    {
//        Class<?> clazz = tableRestrictionBuilder.getClazz();
//        ClassMetadata classMetadata = classMap.get(clazz);
//        if (classMetadata == null)
//        {
//            throw new RuntimeException("Invalid class specified: "+clazz);
//        }
//        Map<PropertyMetadataImpl,Object> fieldMap = new HashMap<PropertyMetadataImpl,Object>();
//        
//        for (String f:tableRestrictionBuilder.getColumns())
//        {
//            PropertyMetadataImpl pm = classMetadata.getField(f);
//            if (pm == null)
//            {
//                throw new RuntimeException("Invalid field name: "+f+" on class "+clazz);
//            }
//            fieldMap.put(pm,pm);
//        }
//        Set<PropertyMetadataImpl> fields = fieldMap.keySet();
//        PropertyMetadataImpl[] propertyMetadataArray = fields.toArray(new PropertyMetadataImpl[fields.size()]);
//        List<TableRestrictionRow> rows = tableRestrictionBuilder.populateTableRestriction(propertyMetadataArray);
//        TableRestriction tr = new TableRestriction(classMetadata,fieldMap,rows);
//        for (PropertyMetadata pm: fieldMap.keySet())
//        {
//            pm.addTableRestriction(tr);
//        }
//    }
//

	public Class<?> getObjectType() {
		return EngineMetadata.class;
	}

	public boolean isSingleton() {
		return true;
	}

    public Map<String,List<ChoiceBase>> getChoicesMap()
    {
        return m_choicesMap;
    }

    public void setMessageSource(final MessageSource arg0)
    {
        m_messageSource = arg0;
        
    }

    public Map<Class<Annotation>, Class<? extends FieldValidator<Annotation>>> getValidatorMap()
    {
        Map<Class<Annotation>,Class<? extends FieldValidator<Annotation>>> ret = new HashMap<Class<Annotation>,Class<? extends FieldValidator<Annotation>>>();
        for (Class<? extends FieldValidator<Annotation>> class_: m_fieldValidators)
        {
            Method[] methods = class_.getMethods();
            for (Method method: methods)
            {
                if (method.getName().equals("init"))
                {
                    final Class<Annotation> p = (Class<Annotation>)method.getParameterTypes()[0];
                    ret.put(p,class_);
                    break;
                }
            }
        }
        return ret;
    }

    private List<Class<? extends FieldValidator<Annotation>>> m_fieldValidators;
    
    public List<Class<? extends FieldValidator<Annotation>>> getFieldValidators()
    {
        return m_fieldValidators;
    }
    public void setFieldValidators(final List<Class<? extends FieldValidator<Annotation>>> fieldValidators)
    {
        m_fieldValidators = fieldValidators;
    }

    public void setChoicesDocument(final Document choicesDocument)
    {
        m_choicesDocument = choicesDocument;
        createChoiceMap(m_choicesDocument);
    }
    public void createChoiceMap(final Document document)
    {
        m_choicesMap = new HashMap<String,List<ChoiceBase>>();
        for (Element choicebases: (List<Element>)document.getRootElement().getChildren("ChoiceList"))
        {
            String name = choicebases.getAttributeValue("name");
            List<ChoiceBase> choiceBases = new ArrayList<ChoiceBase>();
            m_choicesMap.put(name, choiceBases);
            for (Element choicebase: (List<Element>)choicebases.getChildren("Choice"))
            {
                choiceBases.add(new ChoiceBase(choicebase.getText(),choicebase.getAttributeValue("name")));
            }
        }
    }
}
